package com.seguriapp.services;

import static com.google.common.collect.Lists.newArrayList;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;
import org.cometd.bayeux.server.BayeuxServer;
import org.cometd.bayeux.server.ServerChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.seguriapp.dao.ComplaintDao;
import com.seguriapp.dao.CrimeCategoryDao;
import com.seguriapp.dao.MediaContentDao;
import com.seguriapp.dao.UserDao;
import com.seguriapp.entities.Complaint;
import com.seguriapp.entities.CrimeCategory;
import com.seguriapp.entities.MediaContent;
import com.seguriapp.entities.MediaContentType;
import com.seguriapp.util.Channels;
import com.seguriapp.util.CriminalItem;
import com.seguriapp.util.SearchFilterEntity;
import com.seguriapp.util.Util;

/**
 * Complaint Service Implementation
 * 
 * @author Tania Gonzales (taniadgv@gmail.com)
 */
@Service
public class ComplaintServiceImpl implements ComplaintService {

  private static final Logger LOGGER = Logger.getLogger(ComplaintServiceImpl.class);

  @Autowired
  private ComplaintDao complaintDao;

  @Autowired
  private MediaContentDao mediaContentDao;

  @Autowired
  private CrimeCategoryDao crimeCategoryDao;

  @Autowired
  private UserDao userDao;

  @Inject
  private BayeuxServer bayeuxServer;

  public void setComplaintDao(final ComplaintDao denunciaDao) {
    this.complaintDao = denunciaDao;
  }

  public void setMediaContentDao(final MediaContentDao mediaContentDao) {
    this.mediaContentDao = mediaContentDao;
  }

  public void setMediaContentDao(final CrimeCategoryDao crimeCategoryDao) {
    this.crimeCategoryDao = crimeCategoryDao;
  }

  public void setBayeuxServer(final BayeuxServer bayeuxServer) {
    this.bayeuxServer = bayeuxServer;
  }

  public void setUserDao(final UserDao userDao) {
    this.userDao = userDao;
  }

  @Transactional
  public Long saveComplaint(final Complaint complaint) {
    try {
      if (complaint == null) {
        throw new Exception();
      }
      complaintDao.save(complaint);
    } catch (Exception e) {
      LOGGER.error("Error saving complaint", e);
      return null;
    }
    sendBroadcastMessage(complaint);
    return complaint.getId();
  }

  @Transactional
  public Complaint findComplaintById(final Long complaintId) {
    return complaintDao.findById(complaintId);
  }

  @Transactional
  public void saveMediaContent(final MediaContent mediaContent) {
    if (mediaContent != null) {
      mediaContent.setContent(new String(Base64.encodeBase64(mediaContent.getContent().getBytes())));
      mediaContent.setCreatedDate(new Date());
      mediaContentDao.save(mediaContent);
    }
  }

  @Transactional
  public List<CriminalItem> getCriminalActivityMap(final double longitude, final double latitude) {
    return complaintDao.getCriminalActivityMap(longitude, latitude);
  }

  @Transactional
  public List<Complaint> listComplaintsOrderByDate() {
    return complaintDao.listComplaintsOrderByDate();
  }

  @Transactional
  public void setComplaintVisibility(final Long complaintId, final boolean visible) {
    complaintDao.setComplaintVisibility(complaintId, visible);
  }

  @Transactional
  public List<CriminalItem> getCriminalActivityMap(final List<Long> mapOptions, final int zoomLevel, final int deviceId, final int allResults) {
    return complaintDao.getCrminalActivityMap(mapOptions, zoomLevel, deviceId, allResults);
  }

  @Transactional
  public List<Complaint> listComplaintsOrderByColumn(final SearchFilterEntity searchFilter) {
    searchFilter.setAllResults(false);
    final List<Long> entitiesIdsList = getEntitiesIdsList(searchFilter.getUserId());
    searchFilter.setIdsList(entitiesIdsList);
    final List<Complaint> complaintsList = complaintDao.listComplaintsOrderByColumn(searchFilter);
    // from the dao method we get the points inside a rectangle, here we filter
    // it inside a circle.
    if (!searchFilter.isSearchByPolStation() && searchFilter.getLatitude() != 0 && searchFilter.getLongitude() != 0
        && searchFilter.getDistance() != 0) {
      final List<Complaint> newComplaintsList = newArrayList();
      for (final Complaint complaint : complaintsList) {
        if (Util.distance(complaint.getCrimeLatitude(), complaint.getCrimeLongitude(), searchFilter.getLatitude(), searchFilter.getLongitude(), 'K') <= searchFilter
            .getDistance()) {
          newComplaintsList.add(complaint);
        }
      }
      return newComplaintsList;
    } else {
      return complaintsList;
    }
  }

  @Transactional
  public long getComplaintsTotalPages(final SearchFilterEntity searchFilter) {
    searchFilter.setAllResults(true);
    final List<Long> entitiesIdsList = getEntitiesIdsList(searchFilter.getUserId());
    searchFilter.setIdsList(entitiesIdsList);
    final List<Complaint> complaintsList = complaintDao.listComplaintsOrderByColumn(searchFilter);
    // from the dao method we get the points inside a rectangle, here we filter
    // it inside a circle.
    long totalResults = 0;
    long pages = 0;
    if (!searchFilter.isSearchByPolStation() && searchFilter.getLatitude() != 0 && searchFilter.getLongitude() != 0
        && searchFilter.getDistance() != 0) {
      long newComplaintsListSize = 0;
      for (final Complaint complaint : complaintsList) {
        if (Util.distance(complaint.getCrimeLatitude(), complaint.getCrimeLongitude(), searchFilter.getLatitude(), searchFilter.getLongitude(), 'K') <= searchFilter
            .getDistance()) {
          newComplaintsListSize++;
        }
      }
      totalResults = newComplaintsListSize;
    } else {
      totalResults = complaintsList.size();
    }
    if (searchFilter.getPageSize() != 0) {
      pages = totalResults / searchFilter.getPageSize();
      if (totalResults % searchFilter.getPageSize() > 0) {
        pages++;
      }
    }
    return pages;
  }

  @Transactional
  public List<MediaContent> getComplaintMediaContentList(final Long complaintId) {
    return mediaContentDao.getComplaintMediaContentList(complaintId);
  }

  @Transactional
  public List<CrimeCategory> getCrimeCategoryList() {
    return crimeCategoryDao.list();
  }

  @Transactional
  public void saveMediaContent(final Long complaintId, final String fileType, final String fileName) {
    final MediaContent media = new MediaContent();
    LOGGER.info("Saving media content: " + fileName);
    media.setContent(new String(Base64.encodeBase64(fileName.getBytes())));
    media.setType(MediaContentType.getEnumByString(fileType));
    final Complaint complaint = new Complaint();
    complaint.setId(complaintId);
    media.setComplaint(complaint);
    media.setCreatedDate(new Date());
    mediaContentDao.save(media);
  }

  @Transactional
  public String changeComplaintVisibility(final Complaint complaint) {
    try {
      final Complaint newComplaint = complaintDao.findById(complaint.getId());
      newComplaint.setMapVisivility(complaint.getMapVisivility());
      complaintDao.save(newComplaint);
      return Util.TRUE;
    } catch (Exception ex) {
      return Util.FALSE;
    }
  }

  @Transactional
  public List<Complaint> getCriminalActivityMap(final List<Long> crimeCategories) {
    return complaintDao.getCrminalActivityMap(crimeCategories);
  }

  private void sendBroadcastMessage(final Complaint complaint) {
    // send message to web client
    LOGGER.info("Sending broadcast to web client");
    final ServerChannel serverChannel = bayeuxServer.getChannel(Channels.SERVICE_NEW_COMPLAINT);
    if (serverChannel == null) {
      LOGGER.info("Server Channel is null");
      return;
    }
    final Map<String, Object> parametros = new HashMap<String, Object>();
    parametros.put(Util.COMPLAINT_JSON, complaint.toJsonString());
    serverChannel.publish(bayeuxServer.getSession(serverChannel.getId()), parametros, null);
  }

  private List<Long> getEntitiesIdsList(final Long userId) {
    if (userId != null) {
      return userDao.isUserAdmin(userId) ? null : userDao.getUserEntities(userId);
    }
    return null;
  }

}
